package tweetfetch;

import twitter4j.*;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Date;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Qian
 * 
 */
public class Console {
	/**
	 * @param args
	 * @throws TwitterException
	 */

	public static final String dbPath = "jdbc:mysql://pedwork.isi.edu/twitter?"
			+ "user=lord&password=lord";
	// public static final String dbPath = "jdbc:mysql://localhost/twitter?"
	// + "user=root&password=5060379008";

	Twitter twitter = new TwitterFactory().getInstance();
	Connection conn = null;
	Statement stmt = null;
	UserNameQueue queue = new UserNameQueue();
	int HourRequests = 70;

	public static void main(String[] args) {
		System.out.print("Ready to Work\ninit...");
		Console worker = new Console();
		System.out.println("finish!");
		try {
			worker.init_db();
			worker.run();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void init_db() throws SQLException {
		System.out.print("init db...");
		conn = DriverManager.getConnection(dbPath);
		stmt = conn.createStatement();
		queue.init_db();
		System.out.println("finish!");
	}

	public void close_db() throws SQLException {
		stmt.close();
		conn.close();
		queue.close_db();
	}

	/*
	 * During one round, we will see a lot of references in tweets, we append
	 * them into a list. Once we at the end of the one round, we process all
	 * these references: 1. If they are already in the twitter id list, we drop
	 * them. 2. If they don't exist in our system, we do the regular procedure
	 * for each new username
	 */
	public void run() throws Exception {
		//
		// tweets_of_user("toptweets",
		// this.db_get_user_by_username("toptweets") == null);
		process_queue();
	}

	private void process_queue() throws InterruptedException, SQLException {
		LinkedList<Long> requests = new LinkedList<Long>();
		String a = null;
		Random r = new Random();
		a = queue.get_one_from_high_queue();
		while (true) {
			if(a != null){
				tweets_of_user(a);
			}
			requests.add(new Date().getTime());
			if (requests.size() > HourRequests)
				requests.removeFirst();
			if (requests.size() == HourRequests) {
				Long last = requests.getFirst();
				long inteval = (new Date().getTime()) - last;
				if (inteval < 3600000) {
					long wait = 3600000 - inteval + 180000;
					close_db();
					System.out.println("[" + (new Date()).toString()
							+ "]  ========Sleeping " + wait / 60000
							+ " minutes for rate limit=====");
					Thread.sleep(wait); // plus 3 mins
					init_db();
					System.out.println("[" + (new Date()).toString()
							+ "]  ========Recovering from sleep=======");
				}
			}
			Thread.sleep(10000);

			if (r.nextFloat() > 0.4) {
				a = queue.get_one_from_high_queue();
				if (a == null) {
					a = queue.get_one_from_queue();
				}
			} else {
				a = queue.get_one_from_queue();
			}

		}
	}

	/*
	 * Given an username, go through all the tweets of this user for each tweet:
	 * 1. if already in the database, all the later are also in the database,
	 * stop 2. if not in database, pop each tweet into database and check for
	 * references in the tweet, populate all the references into reference list
	 */
	public void tweets_of_user(String username) {
		try {
			System.out.println("[" + (new Date()).toString() + "] "
					+ "Pop out username from queue: " + username);
			List<Tweet> tweets = twitter_api_search("from:" + username);
			int out = 0;
			for (int i = 0; i < tweets.size(); i++) {
				Tweet tweet = tweets.get(i);
				boolean exist = db_check_tweet_id(tweet.getId());
				if (exist)
					break;
				db_add_tweet(tweet.getId(), tweet.getFromUserId(), tweet
						.getCreatedAt(), tweet.getText());
				out += append_referring_twitters(tweet.getText());

			}
			int in = append_back_referring_twitters(username);
			boolean newuser = db_get_user_by_username(username) == null;
			if (newuser && tweets.size() > 0) {
				Date recent = tweets.get(0).getCreatedAt();
				Date oldest = tweets.get(tweets.size() - 1).getCreatedAt();
				User nuser = new User();
				nuser.UserID = tweets.get(0).getFromUserId();
				nuser.UserName = username;
				nuser.LastFetch = (int) (new Date().getTime() / 1000);
				long seconds = (recent.getTime() - oldest.getTime()) / 1000;
				seconds = seconds == 0 ? 3600000 * 24 * 7 : seconds
						/ (tweets.size() - 1);
				nuser.UpdateFreq = (int) seconds;
				nuser.RefOut = out;
				nuser.RefIn = in;
				nuser.NumTweets = tweets.size();
				db_add_user(nuser);
				System.out.println("[" + (new Date()).toString() + "] "
						+ nuser.toString());
			}
			add_topic_queue(username);

		} catch (TwitterException e) {
			System.out.println("TwitterException in tweets_of_user():");
			System.out.println(e.getMessage());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private List<Tweet> twitter_api_search(String querystr)
			throws TwitterException {
		Query query = new Query(querystr);
		query.setRpp(100);
		QueryResult result = twitter.search(query);
		List<Tweet> tweets = result.getTweets();
		return tweets;
	}

	private static Pattern pattern = Pattern.compile("@[a-zA-Z0-9_]*");

	public int append_referring_twitters(String text) {
		Matcher matcher = pattern.matcher(text);
		int cnt = 0;
		while (matcher.find()) {
			String username = matcher.group();
			username = username.substring(1);
			if (db_get_user_by_username(username) == null) {
				queue.add_to_queue(matcher.group().substring(1));
			}
			cnt++;
		}
		return cnt;
	}

	private int append_back_referring_twitters(String username)
			throws TwitterException {
		List<Tweet> tweets = twitter_api_search("@" + username);
		for (Tweet t : tweets) {
			if (db_get_user_by_username(t.getFromUser()) == null)
				queue.add_to_queue(t.getFromUser());
		}
		return tweets.size();
	}

	public void db_add_tweet(long tweetID, int UserID, Date created, String text) {
		try {
			String query = "INSERT INTO tweets VALUES (" + tweetID + ","
					+ UserID + "," + created.getTime() / 1000 + ",'"
					+ text.replace("\\", "\\\\").replace("'", "\\'") + "');";
			int state = stmt.executeUpdate(query);
			if (state == 0) {
				System.out.println("Insert query unsuccessful");
			}
		} catch (SQLException ex) {
			System.out.println("Insert tweet query unsuccessful");
			System.out.println("SQLException: " + ex.getMessage());
			System.out.println("ErrorCode: " + ex.getErrorCode());
		}
	}

	public void db_add_user(User u) {
		try {
			String query = "INSERT INTO users VALUES (" + u.UserID + ","
					+ u.LastFetch + "," + u.UpdateFreq + ",'" + u.UserName
					+ "'," + u.NumTweets + "," + u.RefOut + "," + u.RefIn
					+ ");";
			int state = stmt.executeUpdate(query);
			if (state == 0) {
				System.out.println("Insert query unsuccessful");
			}
		} catch (SQLException ex) {
			System.out.println("Insert user query unsuccessful");
			System.out.println("SQLException: " + ex.getMessage());
			System.out.println("ErrorCode: " + ex.getErrorCode());
		}
	}

	public boolean db_check_tweet_id(long id) {
		boolean exist = false;
		ResultSet rs = null;
		try {
			rs = stmt.executeQuery("SELECT * FROM tweets where TweetID=" + id
					+ " ;");
			if (rs.next()) {
				exist = true;
			}
		} catch (SQLException ex) {
			System.out.println("check tweet id unsuccessful");
			System.out.println("SQLException: " + ex.getMessage());
			System.out.println("ErrorCode: " + ex.getErrorCode());
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException sqlEx) {
				} // ignore
				rs = null;
			}
		}
		return exist;
	}

	public User db_get_user_by_username(String username) {
		User u = null;
		ResultSet rs = null;
		try {
			rs = stmt.executeQuery("SELECT * FROM users where UserName='"
					+ username + "' ;");
			if (rs.next()) {
				u = new User();
				u.UserID = rs.getInt("UserID");
				u.UserName = rs.getString("UserName");
				u.LastFetch = rs.getInt("LastFetch");
				u.UpdateFreq = rs.getInt("UpdateFreq");
			}
		} catch (SQLException ex) {
			System.out.println("get user by username unsuccessful");
			System.out.println("SQLException: " + ex.getMessage());
			System.out.println("ErrorCode: " + ex.getErrorCode());
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException sqlEx) {
				} // ignore
				rs = null;
			}
		}
		return u;
	}

	public LinkedList<User> get_users() {
		LinkedList<User> users = new LinkedList<User>();
		ResultSet rs = null;
		try {
			rs = stmt.executeQuery("SELECT * FROM users;");
			while (rs.next()) {
				User u = new User();
				u.UserID = rs.getInt("UserID");
				u.UserName = rs.getString("UserName");
				u.LastFetch = rs.getInt("LastFetch");
				u.UpdateFreq = rs.getInt("UpdateFreq");
				users.add(u);
			}
		} catch (SQLException ex) {
			System.out.println("SQLException: " + ex.getMessage());
			System.out.println("ErrorCode: " + ex.getErrorCode());
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException sqlEx) {
				} // ignore
				rs = null;
			}
		}
		return users;
	}

	private void add_topic_queue(String userName) throws Exception {
		ResultSet rs = stmt
				.executeQuery("Select UserID from users where username =\""
						+ userName + "\"");
		Statement st = conn.createStatement();
		while (rs.next()) {
			Integer userId = Integer.parseInt(rs.getString("UserID"));
			if (userId > -1) {
				String query = "INSERT INTO topic_queue VALUES ('" + userId + "',"
						+ (new Date()).getTime() / 1000 + ");";
				st.executeUpdate(query);
			}
		}
	}
}
